--[[

    Filename:	auxmsg.lua

    Programmer:	aesiot

    Remark:

--]]

assert( "function" ~= type( on_message ), "auxmsg initialize failed" )

local JSON = require( "JSON" )

local auxlib = require( "auxlib" )

local auxmsg = { _VERSION = "1.0", MSGID_NULL = 0, MSGID_USER = 0x10000 }

local MSGID_RESERVE = 0xffff

local Registry = { [ MSGID_RESERVE ] = {}, Ident = 0 }

on_message = function( ident, param )

	if MSGID_RESERVE == ident then

		local item = Registry[ MSGID_RESERVE ]

		local json = JSON.decode( param )

		if auxlib.is_table( json ) then

			if auxlib.is_nil( json.Src ) and auxlib.is_integer( json.ID ) then

				local proc = item[ json.ID ]

				item[ json.ID ] = nil

				auxlib.pcall( proc, json.Ret )

			elseif auxlib.is_string( json.Src ) and auxlib.is_string( json.Cmd ) and auxlib.is_integer( json.ID ) then

				local proc = item[ json.Cmd ]

				if proc then

					if json.Con then

						Registry.Owner = json.Src

						Registry.Reply = function( result )

							local r = vm.post_message( json.Src, MSGID_RESERVE, auxlib.to_json( { ID = json.ID, Ret = result } ) )

							json.Src = nil

							return r
						end

						local b, r = auxlib.pcall( proc, table.unpack( json.Arg ) )

						if b and Registry.Reply then

							Registry.Reply( r )

						elseif Registry.Reply then

							Registry.Reply( nil )
						end

						Registry.Owner = nil
						Registry.Reply = nil
					else

						auxlib.pcall( proc, table.unpack( json.Arg ) )
					end

				elseif json.Con then

					vm.post_message( json.Src, MSGID_RESERVE, JSON.encode( { ID = json.ID } ) )
				end
			end
		end
	else

		auxlib.pcall( Registry[ ident ], param, ident )
	end
end

--------------------------------------------------------------------------------

function auxmsg.async()

	local result = Registry.Reply

	Registry.Reply = nil

	return result
end

--------------------------------------------------------------------------------

function auxmsg.bind( ident, proc )

	if auxlib.is_integer( ident ) then

		if MSGID_RESERVE == ident then

			error( "unable to bind the predetermined identifier", 2 )
		end

		Registry[ ident ] = proc

	elseif auxlib.is_string( ident ) then

		Registry[ MSGID_RESERVE ][ ident ] = proc
	else

		auxlib.argcheck( false, 1, "(integer or string expected)" )
	end
end

--------------------------------------------------------------------------------

function auxmsg.exist( ident )

	if auxlib.is_integer( ident ) then

		return nil ~= Registry[ ident ]

	elseif auxlib.is_string( ident ) then

		return nil ~= Registry[ MSGID_RESERVE ][ ident ]
	else

		return false
	end
end

--------------------------------------------------------------------------------

function auxmsg.get_sender()

	return Registry.Owner
end

--------------------------------------------------------------------------------

function auxmsg.post( tname, ident, param )

	if MSGID_RESERVE == ident then

		error( "unable to bind the predetermined identifier", 2 )
	end

	if auxlib.is_nil( tname ) then

		tname = vm.get_task_name()
	end

	auxlib.assert( vm.post_message( tname, ident, param ) )
end

--------------------------------------------------------------------------------

local function varg2null( ... )

	local result = { ... }

	local length = auxlib.array_max_index( result )

	if auxlib.is_integer( length ) then

		for index = 1, length do

			if nil == result[ index ] then

				result[ index ] = null
			end
		end
	end

	return result
end

function auxmsg.send( tname, ackfn, label, ... )

	auxlib.argcheck( auxlib.is_string( tname ) or auxlib.is_nil( tname ), 1, "(string expected)" )

	auxlib.argcheck( auxlib.is_string( label ), 3, "(string expected)" )

	if auxlib.is_nil( tname ) then

		tname = vm.get_task_name()
	end

	local ident = Registry.Ident

	while nil ~= Registry[ MSGID_RESERVE ][ ident ] do

		ident = ( ident + 1 ) % math.maxinteger

		auxlib.assert( ident ~= Registry.Ident )
	end

	local pack = { ID = ident, Src = vm.get_task_name(), Cmd = label, Con = ( ackfn or false ), Arg = varg2null( ... ) }

	auxlib.assert( vm.post_message( tname, MSGID_RESERVE, auxlib.to_json( pack ) ) )

	Registry.Ident = ( ident + 1 ) % math.maxinteger

	Registry[ MSGID_RESERVE ][ ident ] = ackfn
end

--------------------------------------------------------------------------------

function auxmsg.broadcast( label, ... )

	for i, v in ipairs( vm.get_task_list() ) do

		if v ~= vm.get_task_name() then

			auxmsg.send( v, nil, label, ... )
		end
	end
end

--------------------------------------------------------------------------------

return auxmsg
